#include <dlfcn.h>
#include <plugin.h>
#include "light.h"
#include "xmlhelper.h"

struct light {
	char *type;
	vec4_t color;
	double att_dist, att_pow;
	mat4_t xform, inv_xform;
	void *data;

	void *so;

	/* free light data */
	void (*free_data)(void*);
	/* extract light data from the xml tree */
	void *(*read_xml)(struct xml_node*);
	/* build an xml tree from the light data */
	struct xml_tree *(*write_xml)(void*);
	/* print information about this light */
	void (*print_info)(void*, FILE*);
	/* get a point in the light */
	vec3_t (*get_point)(void*, mat4_t);
};

static int load_plugin(struct light *lt);

struct light *lt_create(void)
{
	struct light *lt;

	if(!(lt = malloc(sizeof *lt))) {
		return 0;
	}
	memset(lt, 0, sizeof *lt);

	m4_identity(lt->xform);
	m4_identity(lt->inv_xform);
	lt->color = v4_cons(1.0, 1.0, 1.0, 1.0);

	return lt;
}

void lt_free(struct light *lt)
{
	if(!lt) return;

	if(lt->free_data && lt->data) {
		lt->free_data(lt->data);
	}

	if(lt->so) {
		dlclose(lt->so);
	}
	free(lt);
}

int lt_set_type(struct light *lt, const char *type)
{
	char *tmp;

	if(!(tmp = malloc(strlen(type) + 1))) {
		return -1;
	}
	strcpy(tmp, type);

	free(lt->type);
	lt->type = tmp;
	return 0;
}

void lt_print_info(struct light *lt, FILE *fp)
{
	lt->print_info(lt->data, fp);
}

int lt_from_xml(struct light *lt, struct xml_node *xml)
{
	struct xml_node *c;
	struct xml_attr *attr;

	if((attr = xml_get_attr(xml, "type"))) {
		lt_set_type(lt, attr->str);

		if(load_plugin(lt) == -1) {
			return -1;
		}

	} else {
		fprintf(stderr, "light: %s, missing type attribute\n", lt->type);
		return -1;
	}

	c = xml->chld;
	while(c) {
		if(strcmp(c->name, "ltattr") == 0) {
			if((attr = xml_get_attr(c, "color"))) {
				lt->color = v4_cons(attr->vval[0], attr->vval[1], attr->vval[2], attr->vval[3]);
			}
			if((attr = xml_get_attr(c, "att_dist"))) {
				lt->att_dist = attr->fval;
			}
			if((attr = xml_get_attr(c, "att_pow"))) {
				lt->att_pow = attr->fval;
			}

		} else if(strcmp(c->name, "xform") == 0) {
			xmlh_get_xform(lt->xform, c);
			m4_inverse(lt->inv_xform, lt->xform);
		}
		c = c->next;
	}

	if(!(lt->data = lt->read_xml(xml))) {
		return -1;
	}
	return 0;
}

struct xml_node *lt_to_xml(struct light *lt)
{
	return 0;	/* TODO */
}

vec3_t lt_get_point(struct light *lt)
{
	return lt->get_point(lt->data, lt->xform);
}

vec4_t lt_get_color(struct light *lt)
{
	return lt->color;
}

vec4_t lt_get_color_att(struct light *lt, vec3_t pt)
{
	double dist, att;

	if(lt->att_dist == 0.0 && lt->att_pow == 0.0) {
		return lt->color;
	}

	dist = v3_length(v3_sub(pt, lt_get_point(lt)));
	att = 2.0 / (1.0 + pow(dist / lt->att_dist, lt->att_pow));
	return v4_scale(lt->color, att);
}

#define GET_FUNC(name)	\
	if(!(lt->name = dlsym(lt->so, #name))) { \
		fprintf(stderr, "light plugin \"%s\" doesn't provide a \"" #name "\" function\n", lt->type); \
		dlclose(lt->so); \
		return -1; \
	}

static int load_plugin(struct light *lt)
{
	void (*pinfo)(FILE*);

	if(!(lt->so = get_plugin(lt->type))) {
		fprintf(stderr, "missing light plugin for type: %s\n", lt->type);
		return -1;
	}

	fprintf(stderr, "Loading: ");
	if((pinfo = dlsym(lt->so, "plugin_info"))) {
		pinfo(stderr);
	} else {
		fprintf(stderr, "%s plugin (light)\n", lt->type);
	}

	GET_FUNC(free_data);
	GET_FUNC(read_xml);
	GET_FUNC(write_xml);
	GET_FUNC(print_info);
	GET_FUNC(get_point);
	return 0;
}
